﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Lidgren.Network;
using NetBrick.Common;
using NetBrick.Common.Packets;
using NetBrick.Server.Handler;

namespace NetBrick.Server
{
    public abstract class Server : IServer
    {
        private readonly Dictionary<ushort, PacketHandler> _packetHandlers;
        private readonly NetServer _server;

        public readonly Dictionary<NetConnection, BasePeer> Peers;

        public bool ShuttingDown { get; private set; }

        public void Shutdown()
        {
            ShuttingDown = true;
        }

        protected Server(string appName, int port, int maxPlayers)
        {
            Log.Init();
            var config = new NetPeerConfiguration(appName);
            config.EnableMessageType(NetIncomingMessageType.Data);
            config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
            config.EnableMessageType(NetIncomingMessageType.StatusChanged);
            config.Port = port;
            config.MaximumConnections = maxPlayers;
            _server = new NetServer(config);
            _packetHandlers = new Dictionary<ushort, PacketHandler>();
            Peers = new Dictionary<NetConnection, BasePeer>();
            var listenThread = new Thread(() =>
            {
                while (!ShuttingDown)
                {
                    NetIncomingMessage incomingMessage;
                    if ((incomingMessage = _server.ReadMessage()) == null) continue;
                    switch (incomingMessage.MessageType)
                    {
                        case NetIncomingMessageType.DebugMessage:
                        case NetIncomingMessageType.VerboseDebugMessage:
                            Log.Info(incomingMessage.ReadString());
                            break;
                        case NetIncomingMessageType.WarningMessage:
                            Log.Warn(incomingMessage.ReadString());
                            break;
                        case NetIncomingMessageType.ErrorMessage:
                        case NetIncomingMessageType.Error:
                            Log.Error(incomingMessage.ReadString());
                            break;
                        case NetIncomingMessageType.StatusChanged:
                            var status = (NetConnectionStatus) incomingMessage.ReadByte();
                            Log.Info("Status changed. Code: {0}", status);
                            switch (status)
                            {
                                case NetConnectionStatus.Connected:
                                    Peers.Add(incomingMessage.SenderConnection,
                                        CreatePeer(incomingMessage.SenderConnection));
                                    Peers[incomingMessage.SenderConnection].OnConnect();
                                    break;
                                case NetConnectionStatus.Disconnected:
                                    Peers[incomingMessage.SenderConnection].OnDisconnect(incomingMessage.ReadString());
                                    Peers.Remove(incomingMessage.SenderConnection);
                                    break;
                            }
                            break;
                        case NetIncomingMessageType.Data:
                            Object packetData;
                            var packetCode = Packet.Instance.Deserialize(incomingMessage, out packetData);
                            if (!_packetHandlers.ContainsKey(packetCode)) continue;
                            _packetHandlers[packetCode].Process(packetData, Peers[incomingMessage.SenderConnection]);
                            break;
                        case NetIncomingMessageType.ConnectionApproval:
                            incomingMessage.SenderConnection.Approve();
                            break;
                        default:
                            Log.Warn("Unhandled message type {0}", incomingMessage.MessageType);
                            break;
                    }
                }
                // ReSharper disable once FunctionNeverReturns
            });
            listenThread.Start();
            _server.Start();
            InitServer();
        }

        private void InitServer()
        {
            Packet.Instance.Register(NetBrickPacketCode.EstablishEncryptionRequest,
                typeof (EstablishEncryptionPacket.Request));
            Packet.Instance.Register(NetBrickPacketCode.EstablishEncryptionResponse,
                typeof (EstablishEncryptionPacket.Response));
            AddFrameworkPacketHandler(NetBrickPacketCode.EstablishEncryptionRequest,
                new EstablishEncryptionPacketHandler(this));
            Init();
        }

        public BasePeer GetPeer(NetConnection connection)
        {
            return Peers[connection];
        }

        public void BroadcastPacket(ushort packetCode, object packetData, NetDeliveryMethod deliveryMethod)
        {
            _server.SendToAll(Packet.Instance.Serialize(packetCode, packetData, _server.CreateMessage()), deliveryMethod);
        }

        public void SendPacket(ushort packetCode, object packetData, BasePeer peer, NetDeliveryMethod deliveryMethod)
        {
            _server.SendMessage(Packet.Instance.Serialize(packetCode, packetData, _server.CreateMessage()),
                peer.Connection, deliveryMethod);
        }

        public void SendPacket(ushort packetCode, object packetData, List<BasePeer> peers,
            NetDeliveryMethod deliveryMethod)
        {
            var sendList = new List<NetConnection>();
            sendList.AddRange(peers.Select(peer => peer.Connection));
            _server.SendMessage(Packet.Instance.Serialize(packetCode, packetData, _server.CreateMessage()), sendList,
                deliveryMethod, 0);
        }

        private void AddFrameworkPacketHandler(ushort packetCode, PacketHandler packetHandler)
        {
            if (packetCode > 256)
            {
                Log.Warn("Could not add packet handler for code {0}: codes more than 256 reserved for application.",
                    packetCode);
                return;
            }
            _packetHandlers.Add(packetCode, packetHandler);
        }

        public void AddPacketHandler(ushort packetCode, PacketHandler packetHandler)
        {
            if (packetCode <= 256)
            {
                Log.Warn("Could not add packet handler for code {0}: codes 0-256 reserved for framework.", packetCode);
                return;
            }
            _packetHandlers.Add(packetCode, packetHandler);
        }

        public void Kick(BasePeer peer, string reason)
        {
            peer.Connection.Disconnect(reason);
        }

        protected abstract BasePeer CreatePeer(NetConnection connection);
        protected abstract void Init();
    }
}